package in.abip.chisame.model;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * <p>
 * CActiveRecord is the base class for classes representing relational data.
 * 
 * <p>
 * It implements the active record design pattern, a popular Object-Relational
 * Mapping (ORM) technique.
 * 
 * <p>
 * This is my best attempt to implement Yii Active Record in Java.
 * 
 * <p>
 * Important Note:
 * 
 * <ol>
 * <li>All subclass must implement model method.
 * <li>All database field can not be primitive.<br>
 * For now it only support Integer, Long, String and Boolean.
 * <li>Use getter and setter for non database field.<br>
 * Because of my lack of knowledge, I reflect from class field to get value from
 * database. I should analyze the sql create statement to populate field. Better
 * yet, generate field on runtime like php or python does. But I guess that just
 * a dream within a dream in java.<br>
 * Update:<br>
 * It's possible to create field on runtime using asm library, but we must
 * access the field using reflection technique because java didn't know the
 * field exist until runtime. It's too cumbersome if I have to use reflection to
 * access the field. Out of question.
 * </ol>
 * 
 * @author Julot.
 * @since Saturday, January 22nd, 2011.
 */
public abstract class CActiveRecord {
	public Integer _id = null; // All android table must contains this field.

	private String _condition = "";

	protected SQLiteDatabase getDb() {
		return SQLiteDatabase.openDatabase(DatabaseHelper.DB_PATH
				+ DatabaseHelper.DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
	}

	/**
	 * Create new instance of ActiveRecord sub class and populate the field with
	 * value from database.
	 * 
	 * @param cursor
	 *            database cursor.
	 * @return {@link CActiveRecord}
	 */
	private Object create(Cursor cursor) {
		Object model = null;
		try {
			model = getClass().newInstance();
		} catch (IllegalAccessException e1) {
		} catch (InstantiationException e1) {
		}
		Field field = null;
		Object value = null;

		for (Member m : getClass().getFields()) {
			int col = cursor.getColumnIndex(m.getName());
			try {
				field = getClass().getField(m.getName());
			} catch (SecurityException e) {
			} catch (NoSuchFieldException e) {
			}
			String type = field.getType().getSimpleName();
			if (type.equals("Integer")) {
				value = cursor.getInt(col);
			} else if (type.equals("Long")) {
				value = cursor.getLong(col);
			} else if (type.equals("String")) {
				value = cursor.getString(col);
			} else if (type.equals("Boolean")) {
				int temp = cursor.getInt(col);
				if (temp == 1)
					value = true;
				else {
					try {
						String str = cursor.getString(col);
						if (str.equals("Y"))
							value = true;
						else
							value = false;
					} catch (Exception e) {
						value = false;
					}
				}
			}
			try {
				field.set(model, value);
			} catch (IllegalArgumentException e) {
			} catch (IllegalAccessException e) {
			}
		}
		return model;
	}

	/**
	 * Find all record by sql statement. Overloading the method for calling with
	 * params.
	 * 
	 * @param sql
	 *            {@link String} the sql statement.
	 * @return {@link List<Object>}
	 */
	public List<Object> findAllBySql(String sql) {
		List<Object> models = new ArrayList<Object>();

		SQLiteDatabase db = getDb();
		Cursor cursor = db.rawQuery(sql, null);
		if (cursor.moveToFirst()) {
			do {
				models.add(create(cursor));
			} while (cursor.moveToNext());
		}
		if (cursor != null && !cursor.isClosed())
			cursor.close();
		db.close();
		return models;
	}

	/**
	 * Find one record by sql statement.
	 * 
	 * @param sql
	 *            {@link String} the sql statement.
	 * @return {@link Object}
	 */
	public Object findBySql(String sql) {
		SQLiteDatabase db = getDb();
		Cursor cursor = db.rawQuery(sql, null);
		Object object = null;

		if (cursor.moveToFirst())
			object = create(cursor);
		if (cursor != null && !cursor.isClosed())
			cursor.close();
		db.close();
		return object;
	}

	/**
	 * Find record by primary key.
	 * 
	 * @param pk
	 * @return {@link Object}
	 */
	public Object findByPk(Integer pk) {
		StringBuilder sql = new StringBuilder();
		sql.append("SELECT * FROM ");
		sql.append(getClass().getSimpleName());
		sql.append(" WHERE _id = ");
		sql.append(pk.toString());
		// sql.append(";");
		return findBySql(sql.toString());
	}

	/**
	 * Find all record.
	 * 
	 * @return {@link List<Object>}
	 */
	public List<Object> findAll() {
		return findAllBySql(generateSql());
	}

	/**
	 * Find all record.
	 * 
	 * @param condition
	 *            {@link String}
	 * @return {@link List}
	 */
	public List<Object> findAll(String condition) {
		_condition = condition;
		return findAllBySql(generateSql());
	}

	/**
	 * Generate sql statement.
	 * 
	 * @return {@link String}
	 */
	private String generateSql() {
		StringBuilder sql = new StringBuilder();
		sql.append("SELECT * FROM ");
		sql.append(getClass().getSimpleName());
		if (_condition != "") {
			sql.append(" WHERE ");
			sql.append(_condition);
		}
		// sql.append(";");
		return sql.toString();
	}

	protected Boolean beforeSave() {
		return true;
	}

	protected void afterSave() {
	}

	public Boolean save() {
		if (!beforeSave())
			return false;

		Class<?> c = this.getClass();
		String table = c.getSimpleName();

		ContentValues values = new ContentValues();
		Field field = null;
		String fieldName = null;
		String fieldType = null;
		Long lngValue = null;
		Integer intValue = null;
		String strValue = null;
		Boolean blnValue = null;
		for (Member member : c.getDeclaredFields()) {
			fieldName = member.getName();
			if (fieldName.startsWith("_"))
				continue;
			try {
				field = c.getDeclaredField(fieldName);
				fieldType = field.getType().getSimpleName();
				if (fieldType.equals("Integer")) {
					try {
						intValue = (Integer) field.get(this);
						values.put(fieldName, intValue);
					} catch (IllegalArgumentException e) {
					} catch (IllegalAccessException e) {
					}
				} else if (fieldType.equals("Long")) {
					try {
						lngValue = (Long) field.get(this);
						values.put(fieldName, lngValue);
					} catch (IllegalArgumentException e) {
					} catch (IllegalAccessException e) {
					}
				} else if (fieldType.equals("String")) {
					try {
						strValue = (String) field.get(this);
						values.put(fieldName, strValue);
					} catch (IllegalArgumentException e) {
					} catch (IllegalAccessException e) {
					}
				} else if (fieldType.equals("Boolean")) {
					try {
						blnValue = (Boolean) field.get(this);
						values.put(fieldName, blnValue ? 1 : 0);
					} catch (IllegalArgumentException e) {
					} catch (IllegalAccessException e) {
					}
				}
			} catch (SecurityException e) {
			} catch (NoSuchFieldException e) {
			}
		}

		Boolean result = false;
		SQLiteDatabase db = getDb();
		if (this._id == null) {
			Long id = db.insert(table, null, values);
			if (id > 0) {
				try {
					field = c.getField("_id");
					field.set(this, id.intValue());
				} catch (SecurityException e) {
				} catch (NoSuchFieldException e) {
				} catch (IllegalArgumentException e) {
				} catch (IllegalAccessException e) {
				}
				result = true;
			}
		} else {
			String whereClause = "_id=?";
			String[] whereArgs = { _id.toString() };
			result = db.update(table, values, whereClause, whereArgs) > 0;
		}
		afterSave();
		db.close();
		return result;
	}

	public Boolean delete() {
		SQLiteDatabase db = getDb();
		String[] params = new String[] { _id.toString() };
		int r = db.delete(getClass().getSimpleName(), "_id=?", params);
		db.close();
		return r > 0;
	}

}